Story 5.3: the identity engine gets its own abstention cause — and the two mutations the story did not ask for - #48
Merged
Conversation
…o mutations the story did not ask for
`Outcome::Abstained` carries `IdentityAbstentionCause` (new
`opencmdb-core/src/identity/cascade.rs`): two variants, `Ambiguous` and
`AbsenceOfProof`, each traced to a row of D13's six-row table. Four tests pin
its claims. `Expectation::MustAbstain` keeps `gap::AbstentionCause` and
`reconcile`'s enum is byte-unchanged, so no committed artefact was re-hashed —
`git status fixtures/` and `MANIFEST.toml` both verified empty.
This is a TYPE and its tests. There is no cascade, no rule, no `Decision`, no
join and no producer; those are stories 5.4/5.5/5.6.
263 → 267 tests (135 bin + 90 core + 42 xtask). Both clippy forms green,
including the CI one without `--all-targets` — the only invocation that catches
an import kept alive solely by a test module, which is exactly what the two
asymmetric import edits here risked.
SEVEN mutations run, not the five specified, and the two additions are the
point: `all()`'s doc asserts two mechanisms and the five could measure only one.
M1b (a third variant → `error[E0004]` at the witness match) and M1c (widening
the literal → `error[E0308]`, rustc printing "expected
`[IdentityAbstentionCause; 2]` because of return type") measure both, rather
than shipping half a doc sentence as a prediction.
Two prediction/observation differences are recorded rather than smoothed:
· M1 does NOT behave as predicted. Deleting a variant gives `error[E0599]`,
never the predicted `E0308`, and `trap_gate.rs`'s four retyped sites never
red at all — `opencmdb-core` fails to compile first, so `opencmdb-bin` is
never compiled.
· M2 reds EIGHT tests, not the seven predicted. The story missed
`scoring_ignores_the_rule_because_the_trap_runner_owns_it`, whose last
assertion IS the flipped cell. M3's five-red set matched exactly.
M4 is AC4's evidence and was measured on both sides: adding `Ambiguous` to
`gap::AbstentionCause` gives exactly ONE `error[E0004]`, at `page.rs:114:11`,
and under that mutation core still reports 90 passed and xtask 42. Widening the
reconciliation enum therefore costs a user-facing label plus two locale strings
for a variant `reconcile` can never produce — the argument for a separate type,
now measured instead of asserted.
One count in the story's own Dev Notes is falsified by this commit and is
corrected rather than left standing: `Outcome::Abstained` has NINE construction
sites, not six. All nine are still in test modules — M5 measured that
independently, breaking only the `lib test` target.
Register: the story-4.6a bullet is CLOSED by append-and-strike, naming the
branch taken and the tests that hold it, and correcting in the closure note its
own sentence claiming a different outcome-side type "would make comparison
asymmetric" — there is no comparison to go asymmetric. The two `NoMatch`
bullets are annotated PARTLY closed, not struck: the vocabulary exists, the
mapping has no producer until 5.4/5.5. A new `## Deferred from: story-5.3`
section opens five items, each with a named owner.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 10 is checked with the honest state written beside it: branch → PR #48 → CI green in 1m00s (run 30389174367). The squash merge is NOT done and is not dev-story's to do — code-review comes between here and the merge, and `done` is the MERGE's business (5.1 established this; 5.2 and 5.2b confirmed it). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e story turns out to have a green exit
Three parallel layers. NO behavioural defect was found by any of them; the
Acceptance Auditor reproduced SIX of the seven mutations and not one observed
red set differed from the record. What fell is documentation truth, in a story
built to guard exactly that.
THE DECISION. `all()`'s witness stops the build on a new variant (error[E0004])
but does NOT force the variant into the list. Measured by RUNNING the lazy
repair: a bare `Self::NewThing => {}` arm leaves `all()` returning 2 of 3 while
the suite reports 90 passed. Guy's call — keep the mechanism, state its limit
truthfully, register the residue with owner 5.14. Two closures were built and
measured first and both rejected:
· an `ordinal()`/`slot()` witness is GREEN with the variant missing (91
passed) — it closes nothing, because `slot()` is never called on a variant
that is not already in `all()`. This was the reviewer's own first
recommendation, withdrawn on measurement rather than on argument;
· a single-source `macro_rules!` works (`all().len() = 3`) but violates AC1's
literal `[Self; 2]` return type, and makes adding a variant frictionless —
the opposite of AC1b, which calls a third variant a FINDING.
`all()`'s doc now states the guarantee and its limit, and the witness carries a
comment naming the wrong repair for whoever arrives on the E0004.
ALL THREE LAYERS CONVERGED on a false row count in the enum's own guard
paragraph — "the four D13 rows that produce none" when it is two. Inherited from
an AC1b that contradicts itself four lines apart; the dev copied the wrong half
instead of raising it as a finding.
Also patched: `all()`'s doc claiming a new variant breaks it "twice" when the two
errors are alternatives along one repair path; the variant-set test's doc
asserting a guard it does not have; a re-count the story pre-named and skipped
(`Expectation::MustAbstain` 7 → 8, the eighth being this story's own test);
AC1's `Given` clause, refuted by the story's own M4 and traced back to the 4.6a
register bullet, now struck there beside a SECOND false clause that bullet's
first closure had announced but never struck; the cross-vocabulary test widened
from 2 of 6 pairs to all six and RE-PROVEN red under M2; and eight smaller
count, antecedent and placement defects across four documents.
CLAUDE.md's Epic 5 line, stale since 5.2 and 5.2b merged, is fixed here rather
than deferred — the drift was inherited, the occasion is this push.
REVIEWER'S OWN DEFECT, recorded rather than quietly fixed: two layers with write
access ran against ONE working tree, and the Edge Case Hunter observed another
agent's mutation mid-pass. Nothing was corrupted — tree verified clean, md5
stable, gate re-run whole — but the next multi-layer review gives each writing
layer its own git worktree.
Test count UNCHANGED at 267 (135 + 90 + 42): the widened test is still one test,
so the number says nothing about these patches. The re-run red set does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
guycorbaz
added a commit
that referenced
this pull request
Jul 29, 2026
Bookkeeping for story 5.3, after PR #48 was squash-merged as `62f9c83`. - Status → `done` in the story file and in `sprint-status.yaml`. Separate commit on purpose: **`done` is the MERGE's business** in this project's flow, which is why the `code-review` workflow's own default of setting it at the end of the review was not applied — same shape as 5.1, 5.2 and 5.2b. - `docs/project-context.md` — Epic 5's line says 5.3 is done and 5.4 is next; the test count reads **267 on master** (135 + 90 + 42) instead of a "with 5.3 in review" figure the merge made stale. - `CLAUDE.md` — the Epic 5 sentence now names what 5.3 shipped: the identity engine's own abstention vocabulary in the new `crates/opencmdb-core/src/identity/`, and **no engine**. No code changes. `cargo xtask ci` green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What lands
Outcome::AbstainedcarriesIdentityAbstentionCause(newcrates/opencmdb-core/src/identity/cascade.rs): two variants,AmbiguousandAbsenceOfProof, each traced to a row of D13's six-row table. Four tests pin its claims.This is a TYPE and its tests. It writes no engine — no cascade, no rule, no
Decision, no join, no producer. Those are stories 5.4/5.5/5.6.Expectation::MustAbstainkeepsgap::AbstentionCauseandreconcile's enum is byte-unchanged, so no committed artefact was re-hashed:git status fixtures/andfixtures/MANIFEST.tomlboth verified empty.Numbers, re-measured on this tree
263 → 267 tests (135 bin + 90 core + 42 xtask).
cargo xtask ciall green (ℹ views-hash STALE, exit 0 — expected, deliberately not regenerated). Both clippy forms green, including the CI one without--all-targets— the only invocation that catches an import kept alive solely by a test module, which is exactly what the two asymmetric import edits here risked.Seven mutations, not the five specified
The two additions are the point:
all()'s doc asserts two mechanisms and the five specified mutations could measure only one.AmbiguousE0599at 4 sitesE0004, the witness matchE0308, "expected[IdentityAbstentionCause; 2]because of return type"(must-abstain, Abstained) → Fail(must-not-merge, Abstained) → FailAmbiguoustogap::AbstentionCauseE0004atpage.rs:114:11E0308, 3 of them the new guards; lib still compilesTwo prediction/observation differences, recorded rather than smoothed
E0599, never the predictedE0308, andtrap_gate.rs's four retyped sites never red at all —opencmdb-corefails to compile first, soopencmdb-binis never compiled.scoring_ignores_the_rule_because_the_trap_runner_owns_it, whose last assertion is the flipped cell.M4 was measured on both sides: under the mutation, core still reports 90 passed and xtask 42. So widening the reconciliation enum costs a user-facing label plus two locale strings for a variant
reconcilecan never produce — the argument for a separate type, measured instead of asserted.One count corrected by this commit
Outcome::Abstainednow has nine construction sites, not the six the story's Dev Notes measured at12be2fc— this story's own test loops added three. All nine are still in test modules, which M5 measured independently by breaking only thelib testtarget.Register
The story-4.6a bullet is CLOSED by append-and-strike, naming the branch taken and the tests that hold it — and correcting, in the closure note, its own sentence claiming a different outcome-side type "would make comparison asymmetric". There is no comparison to go asymmetric. The two
NoMatchbullets are annotated ↺ PARTLY closed, not struck: the vocabulary exists, the mapping has no producer until 5.4/5.5. A new## Deferred from: story-5.3section opens five items, each with a named owner.🤖 Generated with Claude Code